import json

import os
import re
import importlib
from typing import Union

from conf import settings

from common.utils.parser import jsonparser

from common.utils.parser import YamlLoader, get_case_dir
from common.core.request import RequestCase


# 依赖用例
class DependFamily:
    def __init__(self, test_data: dict, response=None, depend_cases=None):
        self.test_data = test_data
        self.response = response or {}
        self.depend_cases = depend_cases or []

    def __repr__(self):
        return f"<{type(self).__name__}: " \
               f"response: {self.response}, " \
               f"test_data: {self.test_data}, " \
               f"depend_cases: {self.depend_cases}> "


def parse_template(test_case: dict) -> dict:
    """
    param case: from origin
    :return: after analysis
    """

    case_str = json.dumps(test_case, ensure_ascii=False)
    while True:
        ret = re.search(settings.REFLECT_FUNCTION_EXP, case_str)
        if not ret:
            break
        func_name, params = (i.strip() for i in ret.groups())
        module = importlib.import_module(settings.OUTER_QUOTE_MODULE)
        if not hasattr(module, func_name):
            raise AttributeError(f"{settings.OUTER_QUOTE_MODULE}中不存在{func_name}函数")
        if params:  # 解析函数参数
            args = []
            kwargs = {}
            for parm in params.split(","):
                if "=" in parm:
                    key, value = parm.split("=")
                    kwargs.setdefault(key.strip(), value.strip())
                else:
                    args.append(parm.strip())
            value = getattr(module, func_name)(*args, **kwargs)
            case_str = re.sub(settings.REFLECT_FUNCTION_EXP, value, case_str, 1)
        else:
            value = getattr(module, func_name)()
            case_str = re.sub(settings.REFLECT_FUNCTION_EXP, value, case_str, 1)

    while True:
        ret = re.search(settings.REFLECT_VAR_EXP, case_str)
        if not ret:
            break
        key = ret.groups()[0].strip()
        module = importlib.import_module(settings.OUTER_QUOTE_MODULE)
        if not hasattr(module, key):
            raise AttributeError(f"{settings.OUTER_QUOTE_MODULE}中不存在{key}配置")
        value = getattr(module, key)
        case_str = re.sub(settings.REFLECT_VAR_EXP, value, case_str, 1)

    while True:
        ret = re.search(settings.INNER_QUOTE_EXP, case_str)
        value = json.loads(case_str)
        if not ret:
            break
        key_list = [i.strip() for i in ret.groups()[0].split(".")]
        for k in key_list:
            k = k.strip()
            try:
                if k.isdigit():
                    value = value[int(k)]
                else:
                    value = value[k]
            except KeyError:
                raise Exception(f"{'.'.join(key_list)}不能找到对应值，请检查引用是否正确") from None
        case_str = re.sub(settings.INNER_QUOTE_EXP, value, case_str, 1)

    return json.loads(case_str)


class Analysis:
    def __init__(self, case_instance: RequestCase):
        self.case_instance = case_instance
        self.all_cases = {}  # 当前所有的依赖用例

    # 解析当前所有的依赖用例
    def analysis_all_cases(self, case_instance=None, weight=0):

        case_instance = case_instance or self.case_instance
        case_name = f"{case_instance.dir}.{case_instance.title}"
        for case in self.all_cases.keys():
            if case[0] == case_name:
                if case[1] < weight and self.all_cases[case].depend_cases:
                    chain = " -> ".join(
                        (k[0] for k in sorted(self.all_cases.keys(), key=lambda x: x[1]))
                    ) + f" -> {case_name}"
                    raise SyntaxError(f"存在死递归，{chain}")
        case_name = (f"{case_name}", weight)
        if not self.all_cases.get(case_name):
            # 将用例放入关联字典中
            self.all_cases[case_name] = DependFamily(test_data=case_instance.values(), )
            # 解析依赖用例
            ret = re.findall(settings.CASE_QUOTE_EXP, json.dumps(case_instance.values(), ensure_ascii=False))
            if ret:
                for i in ret:
                    depend_case_name = ".".join([item.strip() for item in i[0].split(".")])
                    # no repeat
                    if depend_case_name not in self.all_cases[case_name].depend_cases:
                        self.all_cases[case_name].depend_cases.append(depend_case_name)
                        depend_case_body = self.get_case_body(depend_case_name)
                        self.analysis_all_cases(depend_case_body, weight + 1)  # 递归解析
            else:
                weight = 0

    # 根据用例名称获取
    def get_case_body(self, case_name: str, ) -> RequestCase:
        case_dir, case_title = case_name.split(".")
        case_path = self.analysis_case_path(case_dir)
        if not case_path:
            raise FileNotFoundError(f"目标文件夹{case_dir}不存在")
        for case in YamlLoader(case_path).load_cases():
            case = RequestCase(case)
            if case.title == case_title:
                case.dir = case_dir
                return case
        raise FileNotFoundError(f"{case_path}下不存在title为{case_title}的用例")

    # 解析 case 目录
    def analysis_case_path(self, case_dir: str,
                           root_path=os.path.join(settings.PROJECT_ROOT, settings.TEST_CASE_DIR)) -> Union[str, None]:
        items = os.listdir(root_path)
        for item in items:
            current_path = os.path.join(root_path, item)
            if os.path.isdir(current_path):
                if item == case_dir:
                    for aim in os.listdir(current_path):
                        if aim == settings.CASE_FILE_NAME:
                            return str(os.path.join(current_path, aim))
                    else:
                        raise FileNotFoundError(f"文件夹{case_dir}下未找到{settings.CASE_FILE_NAME}文件")

                if item != case_dir:
                    ret = self.analysis_case_path(case_dir, current_path)
                    if ret:
                        return ret

    # 处理依赖的请求
    def request_process(self):
        self.all_cases = {k[0]: v for k, v in self.all_cases.items()}  # 祛除权重
        if len(self.all_cases) == 1:
            for k in self.all_cases:
                return parse_template(self.all_cases[k].test_data)

        for k, v in self.all_cases.items():  # type: str, DependFamily
            # 解析没有依赖的 case
            if not v.depend_cases:
                case_body = parse_template(v.test_data)
                # 请求并获得 response
                response = RequestCase(case_body).do_request(
                    is_depend=True).assert_res().get_response()
                self.all_cases[k].response = response
        while True:
            for k, v in self.all_cases.items():  # type: str, DependFamily
                if v.depend_cases and v.response == {}:
                    if all([self.all_cases.get(item).response for item in v.depend_cases]):
                        case_str = json.dumps(v.test_data, ensure_ascii=False)

                        while True:
                            ret = re.search(settings.CASE_QUOTE_EXP, case_str)
                            if not ret:
                                break
                            depend_case_name, json_path = ret.groups()
                            depend_case_name = ".".join([item.strip() for item in depend_case_name.split(".")])
                            response = self.all_cases[depend_case_name].response
                            try:
                                value = jsonparser(response, json_path.strip(), )
                            except KeyError:
                                raise KeyError(f"解析json_path失败，response: {response}, json_path：{json_path}")

                            case_str = re.sub(settings.CASE_QUOTE_EXP, value, case_str, 1)
                        case_body = parse_template(json.loads(case_str))
                        # 返回解析用例
                        if len([item for item in self.all_cases.values() if bool(item.response)]) + 1 == len(
                                self.all_cases):
                            return case_body
                        res = RequestCase(case_body).do_request(is_depend=True).assert_res().get_response()
                        self.all_cases[k].response = res


def analysis_case(asb_path: str, origin_case: RequestCase):
    dir_name = get_case_dir(asb_path)
    origin_case.dir = dir_name
    obj = Analysis(origin_case)
    obj.analysis_all_cases()
    return obj.request_process()
